package com.lz.gmall.item.biz.Impl;

import com.alibaba.fastjson.JSON;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import com.lz.gamll.feign.product.SkuDetailFeignClient;
import com.lz.gamll.feign.search.GoodsFeignClient;
import com.lz.gmall.cache.anno.GmallCache;
import com.lz.gmall.constant.GmallConstant;
import com.lz.gmall.item.biz.SkuDetailBizService;
import com.lz.gmall.cache.service.RedisCacheService;
import com.lz.gmall.product.entity.SkuInfo;
import com.lz.gmall.product.entity.SpuSaleAttr;
import com.lz.gmall.product.vo.AttrValueConcatVo;
import com.lz.gmall.product.vo.CategoryViewVo;
import com.lz.gmall.product.vo.SkuDetailVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

@Service
@Slf4j
public class SkuDetailBizServiceImpl implements SkuDetailBizService {

    @Autowired
    private SkuDetailFeignClient skuDetailFeignClient;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    private static final BloomFilter<Long> bloomFilter = BloomFilter.create(Funnels.longFunnel() , 1000000 , 0.00001);

    private static final Lock lock = new ReentrantLock();

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RedisCacheService redisCacheService;

    @Autowired
    private GoodsFeignClient goodsFeignClient;

//    @PostConstruct
//    public void init(){
//        //远程调用获取所有的skuId
//        List<Long> longList = skuDetailFeignClient.getAllStuIdByRpc().getData();
//        //将所有的skuId加入布隆过滤器
//        longList.forEach(aLong -> {
//            bloomFilter.put(aLong);
//        });
//    }
    @Override
    @GmallCache(cacheKey = GmallConstant.REDIS_SKU_INFO+"#{#params[0]}",
            bloomFilterName = GmallConstant.REDIS_REDISSON_BLOOM_FILTER,
            bloomFilterValue = "#{#params[0]}",
            lockName = GmallConstant.REDIS_REDISSON_INFO_LOCK+"#{#params[0]}")
    public SkuDetailVo getSkuDetailBySkuId(Long skuId) {
        return getSkuDetailVo(skuId);
    }

    /**
     *
     * @param skuId 商品id
     */
    @Override
    public void updateHotScore(Long skuId) {
        Long hotScore = redisTemplate.opsForValue().increment("sku:info:hotScore" + skuId);
        if (hotScore % 10 ==0){
            goodsFeignClient.updateHotScore(hotScore,skuId);
        }
    }

    public SkuDetailVo redisCacheService(Long skuId) {

        //防止随机数穿透，先通过布隆过滤器过滤大部分的异常请求
        //boolean contain = bloomFilter.mightContain(skuId);
        RBloomFilter<Long> filter = redissonClient.getBloomFilter(GmallConstant.REDIS_REDISSON_BLOOM_FILTER);
        if (!filter.contains(skuId)){
            //布隆过滤器中没有，说明数据库中绝对没有该数据
            log.info("布隆过滤器中没有数据，直接返回null");
            return null;
        }

        SkuDetailVo detailVo = redisCacheService.getRedisCache(GmallConstant.REDIS_SKU_INFO + skuId, SkuDetailVo.class);

        if (detailVo!=null){
            log.info("redis缓存中有该数据,直接返回");
            return detailVo;
        }
        //通过Redisson获取锁
        RLock rLock = redissonClient.getLock(GmallConstant.REDIS_REDISSON_INFO_LOCK + skuId);

        boolean tryLock = rLock.tryLock();
        if (tryLock){
            try {
                //说明抢到了锁，抢到了锁，去访问数据库
                log.info("线程"+Thread.currentThread().getId()+"抢到了锁，去查询数据库。。");

                //通过远程调用查询数据库，获取SkuDetailVo
                //数据库中也不一定存在该数据，进行判断
                SkuDetailVo skuDetailVo = getSkuDetailVo(skuId);

                if (skuDetailVo == null){
                    log.info("线程"+Thread.currentThread().getId()+"redis缓存，数据库中不存在该数据，直接返回null,并存入x");
                    redisCacheService.saveRedisCache(GmallConstant.REDIS_SKU_INFO+skuId,GmallConstant.REDIS_SKU_INFO_NULL_VALUE);
                }
                log.info("线程"+Thread.currentThread().getId()+"数据库中有该数据，直接返回，并加入redis缓存");
                redisCacheService.saveRedisCache(GmallConstant.REDIS_SKU_INFO+skuId,skuDetailVo);
                return skuDetailVo;
            }catch (Exception e){
                e.printStackTrace();
                return null;
            }finally {
                //释放锁
                //unLock(skuId,uuid);
                rLock.unlock();
            }

        }else {
            log.info("线程"+Thread.currentThread().getId()+"没有抢到锁，去查询redis。。");
            //说明没有抢到锁,先去redis再查一下，看看数据返回了没有
            SkuDetailVo skuDetailVo = redisCacheService.getRedisCache(GmallConstant.REDIS_SKU_INFO + skuId, SkuDetailVo.class);
            if (skuDetailVo != null){
                log.info("线程"+Thread.currentThread().getId()+"查询redis查询到数据，直接返回");
                return skuDetailVo;
            }else {
                //没有抢到线程的锁，查询redis直接查询不到
                log.info("线程"+Thread.currentThread().getId()+"查询redis没有数据，睡眠一段时间，再次查询");
                //让线程睡眠
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //睡醒查询
                SkuDetailVo skuDetailVoAfterSleep = redisCacheService.getRedisCache(GmallConstant.REDIS_SKU_INFO + skuId, SkuDetailVo.class);
                if (skuDetailVoAfterSleep!=null){
                    log.info("线程"+Thread.currentThread().getId()+"睡醒查询到数据，直接返回");
                    return skuDetailVoAfterSleep;
                }
                return null;
            }
        }
    }

    public SkuDetailVo redissonLock(Long skuId) {

        //防止随机数穿透，先通过布隆过滤器过滤大部分的异常请求
        boolean contain = bloomFilter.mightContain(skuId);
        if (!contain){
            //布隆过滤器中没有，说明数据库中绝对没有该数据
            log.info("布隆过滤器中没有数据，直接返回null");
            return null;
        }

        //布隆过滤器中有该id，但是不意味着redis中一定存在
        String jsonSkuInfo = redisTemplate.opsForValue().get(GmallConstant.REDIS_SKU_INFO+skuId);

        if (!StringUtils.isEmpty(jsonSkuInfo)){
            //redis中有该数据，直接返回
            if (GmallConstant.REDIS_SKU_INFO_NULL_VALUE.equalsIgnoreCase(jsonSkuInfo)){
                log.info("redis缓存中有数据x,数据库中没有数据，直接返回null");
                return null;
            }
            log.info("redis缓存中有该数据,直接返回");
            return JSON.parseObject(jsonSkuInfo, SkuDetailVo.class);
        }

        //解决缓存击穿的问题，进行加锁，在分布式的访问redis击穿访问数据库，加锁只让一次请求去访问数据库，并加入redis
        //其他线程不进行堵塞，先让他重新访问redis，如果redis还没有，就睡眠等待

        //加锁，但是不会阻塞其他线程
        //boolean tryLock = lock.tryLock();

        //修改，采用分布式锁
//        String uuid = UUID.randomUUID().toString().replace("-", "");
//        Boolean tryLock = tryLock(skuId, uuid);

        RLock rLock = redissonClient.getLock(GmallConstant.REDIS_REDISSON_INFO_LOCK + skuId);

        boolean tryLock = rLock.tryLock();
        if (tryLock){

            try {
                //说明抢到了锁，抢到了锁，去访问数据库
                log.info("线程"+Thread.currentThread().getId()+"抢到了锁，去查询数据库。。");

                //通过远程调用查询数据库，获取SkuDetailVo
                //数据库中也不一定存在该数据，进行判断
                SkuDetailVo skuDetailVo = getSkuDetailVo(skuId);

                if (skuDetailVo == null){
                    log.info("线程"+Thread.currentThread().getId()+"redis缓存，数据库中不存在该数据，直接返回null,并存入x");
                    redisTemplate.opsForValue().set(GmallConstant.REDIS_SKU_INFO+skuId,GmallConstant.REDIS_SKU_INFO_NULL_VALUE);
                    //return null;
                }
                log.info("线程"+Thread.currentThread().getId()+"数据库中有该数据，直接返回，并加入redis缓存");
                redisTemplate.opsForValue().set(GmallConstant.REDIS_SKU_INFO+skuId,JSON.toJSONString(skuDetailVo));

                return skuDetailVo;
            }catch (Exception e){
                e.printStackTrace();
                return null;
            }finally {
                //释放锁
                //unLock(skuId,uuid);
                rLock.unlock();
            }

        }else {
            log.info("线程"+Thread.currentThread().getId()+"没有抢到锁，去查询redis。。");

            //说明没有抢到锁,先去redis再查一下，看看数据返回了没有
            String jsonSkuInfo2 = redisTemplate.opsForValue().get(GmallConstant.REDIS_SKU_INFO + skuId);

            if (StringUtils.isEmpty(jsonSkuInfo2)){
                //没有抢到线程的锁，查询redis直接查询不到
                log.info("线程"+Thread.currentThread().getId()+"查询redis没有数据，睡眠一段时间，再次查询");

                //让线程睡眠
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //睡醒查询
                String jsonSkuInfo3 = redisTemplate.opsForValue().get(GmallConstant.REDIS_SKU_INFO + skuId);
                if (GmallConstant.REDIS_SKU_INFO_NULL_VALUE.equalsIgnoreCase(jsonSkuInfo3)){
                    log.info("线程"+Thread.currentThread().getId()+"睡醒查询得到x，直接返回null");
                    return null;
                }else {
                    log.info("线程"+Thread.currentThread().getId()+"睡醒查询到数据，直接返回");
                    return JSON.parseObject(jsonSkuInfo3, SkuDetailVo.class);
                }
            }else {
                //没有抢到线程的锁，查询redis直接查询到
                if (GmallConstant.REDIS_SKU_INFO_NULL_VALUE.equalsIgnoreCase(jsonSkuInfo2)){
                    return null;
                }else {
                    log.info("线程"+Thread.currentThread().getId()+"查询redis查询到数据，直接返回");
                    return JSON.parseObject(jsonSkuInfo2,SkuDetailVo.class);
                }
            }
        }
    }
    /**
     * 远程调用接口开发，web-all调用改接口，该接口去调用service-product实现的五个接口
     * 拿到Sku的详细数据
     * @param skuId
     * @return
     */
    public SkuDetailVo redisLock(Long skuId) {

        //防止随机数穿透，先通过布隆过滤器过滤大部分的异常请求
        boolean contain = bloomFilter.mightContain(skuId);
        if (!contain){
            //布隆过滤器中没有，说明数据库中绝对没有该数据
            log.info("布隆过滤器中没有数据，直接返回null");
            return null;
        }

        //布隆过滤器中有该id，但是不意味着redis中一定存在
        String jsonSkuInfo = redisTemplate.opsForValue().get(GmallConstant.REDIS_SKU_INFO+skuId);
        if (!StringUtils.isEmpty(jsonSkuInfo)){
            //redis中有该数据，直接返回
            if (GmallConstant.REDIS_SKU_INFO_NULL_VALUE.equalsIgnoreCase(jsonSkuInfo)){
                log.info("redis缓存中有数据x,数据库中没有数据，直接返回null");
                return null;
            }
            log.info("redis缓存中有该数据,直接返回");
            return JSON.parseObject(jsonSkuInfo, SkuDetailVo.class);
        }

        //解决缓存击穿的问题，进行加锁，在分布式的访问redis击穿访问数据库，加锁只让一次请求去访问数据库，并加入redis
        //其他线程不进行堵塞，先让他重新访问redis，如果redis还没有，就睡眠等待

        //加锁，但是不会阻塞其他线程
        //boolean tryLock = lock.tryLock();

        //修改，采用分布式锁
        String uuid = UUID.randomUUID().toString().replace("-", "");

        Boolean tryLock = tryLock(skuId, uuid);
        if (tryLock){

            try {
                //说明抢到了锁，抢到了锁，去访问数据库
                log.info("线程"+Thread.currentThread().getId()+"抢到了锁，去查询数据库。。");

                //通过远程调用查询数据库，获取SkuDetailVo
                //数据库中也不一定存在该数据，进行判断
                SkuDetailVo skuDetailVo = getSkuDetailVo(skuId);
                if (skuDetailVo == null){
                    log.info("线程"+Thread.currentThread().getId()+"redis缓存，数据库中不存在该数据，直接返回null,并存入x");
                    redisTemplate.opsForValue().set(GmallConstant.REDIS_SKU_INFO+skuId,GmallConstant.REDIS_SKU_INFO_NULL_VALUE);
                    //return null;
                }
                log.info("线程"+Thread.currentThread().getId()+"数据库中有该数据，直接返回，并加入redis缓存");
                redisTemplate.opsForValue().set(GmallConstant.REDIS_SKU_INFO+skuId,JSON.toJSONString(skuDetailVo));
                return skuDetailVo;
            }catch (Exception e){
                e.printStackTrace();
                return null;
            }finally {
                //释放锁
                unLock(skuId,uuid);
            }

        }else {
            log.info("线程"+Thread.currentThread().getId()+"没有抢到锁，去查询redis。。");

            //说明没有抢到锁,先去redis再查一下，看看数据返回了没有
            String jsonSkuInfo2 = redisTemplate.opsForValue().get(GmallConstant.REDIS_SKU_INFO + skuId);

            if (StringUtils.isEmpty(jsonSkuInfo2)){
                //没有抢到线程的锁，查询redis直接查询不到
                log.info("线程"+Thread.currentThread().getId()+"查询redis没有数据，睡眠一段时间，再次查询");

                //让线程睡眠
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //睡醒查询
                String jsonSkuInfo3 = redisTemplate.opsForValue().get(GmallConstant.REDIS_SKU_INFO + skuId);
                if (GmallConstant.REDIS_SKU_INFO_NULL_VALUE.equalsIgnoreCase(jsonSkuInfo3)){
                    log.info("线程"+Thread.currentThread().getId()+"睡醒查询得到x，直接返回null");
                    return null;
                }else {
                    log.info("线程"+Thread.currentThread().getId()+"睡醒查询到数据，直接返回");
                    return JSON.parseObject(jsonSkuInfo3, SkuDetailVo.class);
                }
            }else {
                //没有抢到线程的锁，查询redis直接查询到
                if (GmallConstant.REDIS_SKU_INFO_NULL_VALUE.equalsIgnoreCase(jsonSkuInfo2)){
                    return null;
                }else {
                    log.info("线程"+Thread.currentThread().getId()+"查询redis查询到数据，直接返回");
                    return JSON.parseObject(jsonSkuInfo2,SkuDetailVo.class);
                }
            }
        }
    }

    private Boolean tryLock(Long skuId,String uuid){
        //直接设置过期时间，当业务代码执行时间大于过期时间，也会出现线程
        Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent("lock" + skuId, uuid, 10, TimeUnit.SECONDS);

        //setIfAbsent() 方法返回值的类型为 Boolean。如果在进行自动拆箱时，返回的值为 null，就会抛出空指针异常
        if (Boolean.TRUE.equals(setIfAbsent)){
            //使用Executors工具类可以快速创建线程池。
            ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
            scheduledThreadPool.scheduleAtFixedRate(()->{
                //设定具体的过期时间
                redisTemplate.expire("lock",10,TimeUnit.SECONDS);
            },3,3,TimeUnit.SECONDS);
        }

        return setIfAbsent;
    }

    private void unLock(Long skuId,String uuid){

        //在架构中，要保证判断和删除的原子性，可能出现判断成功，再删除的中间出现锁过期，到达删除锁的时候释放别人的锁
        //采用lua脚本实现原子性,lua脚本已经表示了删除功能
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                "then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        //不要使用 DEL 释放锁，而是发送一个脚本，该脚本仅在值匹配时才删除密钥。
        Long aLong = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList("lock"), uuid);
        if (0 == aLong){
            log.error("锁是别人的，删除失败.....");
        }else {
            log.info("锁是自己的，删除成功....");
        }

        //为了避免锁过期，删除其他线程的锁，进行判断
        /*if (uuid.equalsIgnoreCase(redisTemplate.opsForValue().get("lock"))){
            redisTemplate.delete("lock"+skuId);
        }else {
            log.info("锁已经过期，删除失败.....");
        }*/

    }

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;  //注入自定义线程池对象

    private SkuDetailVo getSkuDetailVo(Long skuId){

        SkuDetailVo skuDetailVo = new SkuDetailVo();

        //获取sku的多种信息(SkuInfo)
        SkuInfo skuInfo = skuDetailFeignClient.getSkuInfoAndImageById(skuId).getData();
        //避免redis穿透中，布隆过滤器中存在的id，数据库（skuInfo）中不一定存在
        if(skuInfo==null){
            return null;
        }
        skuDetailVo.setSkuInfo(skuInfo);

        CompletableFuture<Void> cf1 = CompletableFuture.runAsync(() -> {
            //获取sku对应的一级，二级，三级产品
            CategoryViewVo categoryViewVo = skuDetailFeignClient.getCategoryViewBySkuId(skuId).getData();
            skuDetailVo.setCategoryView(categoryViewVo);
            log.info(Thread.currentThread().getName() + "线程执行了,获取sku对应的一级，二级，三级产品");
        },threadPoolExecutor);

        CompletableFuture<Void> cf2 = CompletableFuture.runAsync(() -> {
            //从中获取price，因为价格涉及金钱，我们重新查询保证实时性
            SkuInfo skuInfo1 = skuDetailFeignClient.getSkuInfoPriceBySkuId(skuId).getData();
            BigDecimal price = skuInfo1.getPrice();
            skuDetailVo.setPrice(price);
            log.info(Thread.currentThread().getName()+"线程执行了,从中获取price，因为价格涉及金钱，我们重新查询保证实时");
        },threadPoolExecutor);

        CompletableFuture<Void> cf3 = CompletableFuture.runAsync(() -> {
            //获取spu的销售属性和销售属性值
            List<SpuSaleAttr> spuSaleAttrList = skuDetailFeignClient.getSpuSaleAttrListBySkuId(skuId).getData();
            skuDetailVo.setSpuSaleAttrList(spuSaleAttrList);
            log.info(Thread.currentThread().getName()+"线程执行了,获取spu的销售属性和销售属性值");
        },threadPoolExecutor);

        CompletableFuture<Void> cf4 = CompletableFuture.runAsync(() -> {
            //获取spuId,根据spuId获取所有的sku,用于页面的转换
            List<AttrValueConcatVo> attrValueConcatVoList = skuDetailFeignClient.getAttrValueConcatBySkuId(skuId).getData();
            Map<String, Long> collect = attrValueConcatVoList.stream().collect(
                    Collectors.toMap(AttrValueConcatVo::getAttrValueConcat, AttrValueConcatVo::getSkuId)
            );
            String jsonString = JSON.toJSONString(collect);
            skuDetailVo.setValuesSkuJson(jsonString);
            log.info(Thread.currentThread().getName()+"线程执行了,获取spuId,根据spuId获取所有的sku,用于页面的转换");
        },threadPoolExecutor);

        CompletableFuture.allOf(cf1,cf2,cf3,cf4).join(); //当这四个线程都执行完成再执行下面的代码，否则就阻塞线程
                                                        // 当这四个接口中出现异常，就会抛出异常，不会执行下面的代码

        log.info(Thread.currentThread().getName()+"线程执行了，所有线程执行完之后最后执行");
        return skuDetailVo;
    }
    private SkuDetailVo getSkuDetailVoByCountDownLatch(Long skuId){

        SkuDetailVo skuDetailVo = new SkuDetailVo();

        CountDownLatch countDownLatch = new CountDownLatch(4); //开启了四个子线程

        //获取sku的多种信息(SkuInfo)
        SkuInfo skuInfo = skuDetailFeignClient.getSkuInfoAndImageById(skuId).getData();
        //避免redis穿透中，布隆过滤器中存在的id，数据库（skuInfo）中不一定存在
        if(skuInfo==null){
            return null;
        }
        skuDetailVo.setSkuInfo(skuInfo);

        threadPoolExecutor.submit(()->{
            //获取sku对应的一级，二级，三级产品
            CategoryViewVo categoryViewVo = skuDetailFeignClient.getCategoryViewBySkuId(skuId).getData();
            skuDetailVo.setCategoryView(categoryViewVo);
            log.info(Thread.currentThread().getName()+"线程执行了,获取sku对应的一级，二级，三级产品");
            countDownLatch.countDown();
        });

        threadPoolExecutor.submit(()->{
            //从中获取price，因为价格涉及金钱，我们重新查询保证实时性
            SkuInfo skuInfo1 = skuDetailFeignClient.getSkuInfoPriceBySkuId(skuId).getData();
            BigDecimal price = skuInfo1.getPrice();
            skuDetailVo.setPrice(price);
            log.info(Thread.currentThread().getName()+"线程执行了,从中获取price，因为价格涉及金钱，我们重新查询保证实时");
            countDownLatch.countDown();
        });

        threadPoolExecutor.submit(()->{
            //获取spu的销售属性和销售属性值
            List<SpuSaleAttr> spuSaleAttrList = skuDetailFeignClient.getSpuSaleAttrListBySkuId(skuId).getData();
            skuDetailVo.setSpuSaleAttrList(spuSaleAttrList);
            log.info(Thread.currentThread().getName()+"线程执行了,获取spu的销售属性和销售属性值");
            countDownLatch.countDown();
        });

        threadPoolExecutor.submit(()->{
            //获取spuId,根据spuId获取所有的sku,用于页面的转换
            List<AttrValueConcatVo> attrValueConcatVoList = skuDetailFeignClient.getAttrValueConcatBySkuId(skuId).getData();
            Map<String, Long> collect = attrValueConcatVoList.stream().collect(
                    Collectors.toMap(AttrValueConcatVo::getAttrValueConcat, AttrValueConcatVo::getSkuId)
            );
            String jsonString = JSON.toJSONString(collect);
            skuDetailVo.setValuesSkuJson(jsonString);
            log.info(Thread.currentThread().getName()+"线程执行了,获取spuId,根据spuId获取所有的sku,用于页面的转换");
            countDownLatch.countDown();
        });

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.info(Thread.currentThread().getName()+"线程执行了，所有线程执行完之后最后执行");
        return skuDetailVo;
    }
}
